package golog

import (
	"bytes"
	"errors"
	"fmt"
	"gitee.com/ymofen/golog/internal"
	"io"
	"os"
	"path"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type GoLogLeveler interface {
	Level() GoLogLevel
	String() string
}

type GoLogOption struct {
	AddSource bool
	Prefix    string
	Suffix    string
	CallSkip  int // base
	Level     GoLogLevel
}

type GoLogOptionFunc func(opt *GoLogOption)

func WithOptionAddSource(v bool) GoLogOptionFunc {
	return func(opt *GoLogOption) {
		opt.AddSource = v
	}
}

func WithOptionPrefix(v string) GoLogOptionFunc {
	return func(opt *GoLogOption) {
		opt.Prefix = v
	}
}

func WithOptionSuffix(v string) GoLogOptionFunc {
	return func(opt *GoLogOption) {
		opt.Suffix = v
	}
}

func WithOptionCallSkip(v int) GoLogOptionFunc {
	return func(opt *GoLogOption) {
		opt.CallSkip = v
	}
}

func WithOptionLevel(v GoLogLevel) GoLogOptionFunc {
	return func(opt *GoLogOption) {
		opt.Level = v
	}
}

type GoLogLevel int

const (
	LevelDebug GoLogLevel = -4
	LevelInfo  GoLogLevel = 0
	LevelWarn  GoLogLevel = 4
	LevelError GoLogLevel = 8
)

const (
	GoLogNormalFormat = "[%d %t] [%L] %M (%S)\r\n"
)

func (l GoLogLevel) Level() GoLogLevel {
	return l
}

func (l GoLogLevel) String() string {
	str := func(base string, val GoLogLevel) string {
		if val == 0 {
			return base
		}
		return fmt.Sprintf("%s%+d", base, val)
	}

	switch {
	case l < LevelInfo:
		return str("DEBUG", l-LevelDebug)
	case l < LevelWarn:
		return str("INFO", l-LevelInfo)
	case l < LevelError:
		return str("WARN", l-LevelWarn)
	default:
		return str("ERROR", l-LevelError)
	}
}

func ParseGoLogLevel(s string) (l GoLogLevel, err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("golog: level string %q: %w", s, err)
		}
	}()

	name := s
	offset := 0
	if i := strings.IndexAny(s, "+-"); i >= 0 {
		name = s[:i]
		offset, err = strconv.Atoi(s[i:])
		if err != nil {
			return LevelDebug, err
		}
	}
	switch strings.ToUpper(name) {
	case "DEBUG":
		l = LevelDebug
	case "INFO":
		l = LevelInfo
	case "WARN":
		l = LevelWarn
	case "ERROR":
		l = LevelError
	default:
		return LevelDebug, errors.New("unknown name")
	}
	l += GoLogLevel(offset)
	return
}

type GoLogRecord struct {
	Level       GoLogLeveler // The log level
	CreatedTime time.Time    // The time at which the log message was created (nanoseconds)
	SourcePC    uintptr      // The message source
	Msg         string
}

type LogHandler interface {
	LogHandle(msg *GoLogRecord, opt *GoLogOption)
}

type GoLog struct {
	lk      *sync.RWMutex
	opt     GoLogOption // 创建时确定好, 不允许更改
	handler LogHandler
}

type NullWriter struct {
}

func (w *NullWriter) Write(p []byte) (n int, err error) {
	return 0, nil
}

type GoLogTextHandler struct {
	w   io.Writer
	fmt string
}

func (h *GoLogTextHandler) LogHandle(msg *GoLogRecord, opt *GoLogOption) {
	var msgBuf []byte
	if len(h.fmt) == 0 {
		var bb bytes.Buffer
		bb.WriteString(FmtLongDate(msg.CreatedTime))
		bb.WriteString(" ")
		bb.WriteString(FmtLongTime(msg.CreatedTime))
		bb.WriteString("\t")
		bb.WriteString(msg.Level.String())
		if len(opt.Prefix) > 0 {
			bb.WriteString("\t")
			bb.WriteString(opt.Prefix)
		}
		bb.WriteString("\t")
		bb.WriteString(msg.Msg)
		if len(opt.Suffix) > 0 {
			bb.WriteString("\t")
			bb.WriteString(opt.Suffix)
		}

		if opt.AddSource {
			bb.WriteString("\t")
			src := GetGoLogSource(msg.SourcePC)
			bb.WriteString(fmt.Sprintf("(%s:%d)", path.Base(src.Function), src.Line))
		}
		bb.WriteString("\r\n")
		//bb.WriteString(fmt.Sprintf("%s(%s):%d", path.Base(src.File), src.Function, src.Line))
		msgBuf = bb.Bytes()
	} else {
		msgBuf = FormatLogRecord(h.fmt, msg)
	}

	h.w.Write([]byte(msgBuf))
}

func WithFormatAndWriterHandler(w io.Writer, fmt string) LogHandler {
	return &GoLogTextHandler{w: w, fmt: fmt}
}

type WriteFunc func([]byte) (int, error)

type WriterWrapper struct {
	fn interface{}
}

func NewWriterWrapper(fn WriteFunc) io.Writer {
	return &WriterWrapper{fn: fn}
}

func (w *WriterWrapper) Write(p []byte) (n int, err error) {
	if fn, ok := w.fn.(WriteFunc); ok {
		return fn(p)
	} else if fn, ok := w.fn.(func(p []byte)); ok {
		fn(p)
		return len(p), nil
	}
	return 0, fmt.Errorf("unknown type %T", w.fn)
}

var (
	recPool = sync.Pool{
		New: func() any {
			return &GoLogRecord{}
		},
	}

	defaultLogger atomic.Pointer[GoLog]

	nullWriter     = &NullWriter{}
	nullLogHandler = WithFormatAndWriterHandler(nullWriter, "")
)

type GoLogSource struct {
	// Function is the package path-qualified function name containing the
	// source line. If non-empty, this string uniquely identifies a single
	// function in the program. This may be the empty string if not known.
	Function string `json:"function"`
	// File and Line are the file name and line number (1-based) of the source
	// line. These may be the empty string and zero, respectively, if not known.
	File string `json:"file"`
	Line int    `json:"line"`
}

func GetGoLogSource(pc uintptr) *GoLogSource {
	fs := runtime.CallersFrames([]uintptr{pc})
	f, _ := fs.Next()
	return &GoLogSource{
		Function: f.Function,
		File:     f.File,
		Line:     f.Line,
	}
}

func FmtLongTime(time time.Time) string {
	zone, _ := time.Zone()
	return fmt.Sprintf("%02d:%02d:%02d %s", time.Hour(), time.Minute(), time.Second(), zone)
}

func FmtShortTime(time time.Time) string {
	return fmt.Sprintf("%02d:%02d", time.Hour(), time.Minute())
}

func FmtLongDate(time time.Time) string {
	return fmt.Sprintf("%04d-%02d-%02d", time.Year(), time.Month(), time.Day())
}

func FmtShortDate(time time.Time) string {
	return fmt.Sprintf("%02d-%02d-%02d", time.Year()%100, time.Month(), time.Day())
}

func FmtELongDate(time time.Time) string {
	return fmt.Sprintf("%04d%02d%02d", time.Year(), time.Month(), time.Day())
}

func FmtEShortDate(time time.Time) string {
	return fmt.Sprintf("%02d%02d%02d", time.Year(), time.Month(), time.Day())
}

func GetGoLogFormatToken(rec *GoLogRecord, token string) (data string, n int) {
	switch token[0] {
	case 'T':
		return FmtLongTime(rec.CreatedTime), 1
	case 't':
		return FmtShortTime(rec.CreatedTime), 1
	case 'D':
		return FmtLongDate(rec.CreatedTime), 1
	case 'd':
		return FmtShortDate(rec.CreatedTime), 1
	case 'E':
		return FmtELongDate(rec.CreatedTime), 1
	case 'e':
		return FmtEShortDate(rec.CreatedTime), 1
	case 'L':
		return rec.Level.String(), 1
	case 'S':
		src := GetGoLogSource(rec.SourcePC)
		return fmt.Sprintf("%s:%d", src.Function, src.Line), 1
	case 's':
		src := GetGoLogSource(rec.SourcePC)
		return fmt.Sprintf("%s:%d", src.Function, src.Line), 1
	case 'M':
		return rec.Msg, 1
	}

	return token, len(token)
}

func ParseGoLogFormat(format string, getTokenValue func(token []byte) (data []byte, n int)) []byte {
	if len(format) == 0 {
		return nil
	}

	// Split the string into pieces by % signs
	pieces := bytes.Split([]byte(format), []byte{'%'})

	out := bytes.NewBuffer(make([]byte, 0, 64))

	// Iterate over the pieces, replacing known formats
	for i, piece := range pieces {
		if i > 0 && len(piece) > 0 {
			data, n := getTokenValue(piece)
			out.Write(data)
			out.Write(piece[n:])
		} else if len(piece) > 0 {
			out.Write(piece)
		}
	}
	return out.Bytes()
}

// Known format codes:
// %T - Time (15:04:05 MST)
// %t - Time (15:04)
// %D - Date (2006/01/02)
// %d - Date (01/02/06)
// %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
// %S - Source
// %M - Message
// %P - typestr
// %E - Date (20060102)
// %e - Date (0102)

// Ignores unknown formats
// Recommended: "[%D %T] [%L] (%S) %M"
func FormatLogRecord(format string, rec *GoLogRecord) []byte {
	if rec == nil {
		return nil
	}
	if len(format) == 0 {
		return nil
	}

	// Split the string into pieces by % signs
	pieces := bytes.Split([]byte(format), []byte{'%'})

	out := bytes.NewBuffer(make([]byte, 0, 64))

	// Iterate over the pieces, replacing known formats
	for i, piece := range pieces {
		if i > 0 && len(piece) > 0 {
			switch piece[0] {
			case 'T':
				out.WriteString(FmtLongTime(rec.CreatedTime))
			case 't':
				out.WriteString(FmtShortTime(rec.CreatedTime))
			case 'D':
				out.WriteString(FmtLongDate(rec.CreatedTime))
			case 'd':
				out.WriteString(FmtShortDate(rec.CreatedTime))
			case 'E':
				out.WriteString(FmtELongDate(rec.CreatedTime))
			case 'e':
				out.WriteString(FmtEShortDate(rec.CreatedTime))
			case 'L':
				s := rec.Level.String()
				out.WriteString(s)
			case 'S':
				src := GetGoLogSource(rec.SourcePC)
				out.WriteString(fmt.Sprintf("%s:%d", src.Function, src.Line))
			case 's':
				src := GetGoLogSource(rec.SourcePC)
				out.WriteString(fmt.Sprintf("%s:%d", src.Function, src.Line))
			case 'M':
				out.WriteString(rec.Msg)
			}
			if len(piece) > 1 {
				out.Write(piece[1:])
			}
		} else if len(piece) > 0 {
			out.Write(piece)
		}
	}
	return out.Bytes()
}

func DefaultLogger() *GoLog {
	return defaultLogger.Load()
}

func Default() *GoLog {
	return defaultLogger.Load()
}

var (
	goLoggerAliveN int32
)

func GetGoLoggerAliveN() int32 {
	return atomic.LoadInt32(&goLoggerAliveN)
}

func NewGoLog(handler LogHandler) *GoLog {
	rval := &GoLog{
		lk:      &sync.RWMutex{},
		handler: handler,
		opt:     GoLogOption{AddSource: true, Level: LevelInfo, CallSkip: 0},
	}
	if rval.handler == nil {
		rval.handler = nullLogHandler
	}
	atomic.AddInt32(&goLoggerAliveN, 1)
	runtime.SetFinalizer(rval, func(obj interface{}) {
		atomic.AddInt32(&goLoggerAliveN, -1)
	})
	return rval
}

func (this *GoLog) getLogRecord() *GoLogRecord {
	return recPool.Get().(*GoLogRecord)
}

func (this *GoLog) clone() *GoLog {
	rval := &GoLog{
		handler: this.handler,
		lk:      this.lk,
		opt:     this.opt,
	}
	atomic.AddInt32(&goLoggerAliveN, 1)
	runtime.SetFinalizer(rval, func(obj interface{}) {
		atomic.AddInt32(&goLoggerAliveN, -1)
	})
	return rval
}

func (this *GoLog) GetCallSkip() int {
	return this.opt.CallSkip
}

func (this *GoLog) WithCallSkip(skip int) *GoLog {
	r := this.clone()
	r.opt.CallSkip = skip
	return r
}

func (this *GoLog) WithHandler(fn LogHandler) *GoLog {
	r := this.clone()
	r.handler = fn
	if r.handler == nil {
		r.handler = nullLogHandler
	}
	return r
}

func (this *GoLog) WithOptionEx(opt ...GoLogOptionFunc) *GoLog {
	r := this.clone()
	for _, fn := range opt {
		fn(&r.opt)
	}
	return r
}

func (this *GoLog) WithOption(opt GoLogOption) *GoLog {
	r := this.clone()
	r.opt = opt
	return r
}

// callskip：最终的, 需要调用者处理
func (this *GoLog) innerLogMsg(lvl GoLogLeveler, callSkip int, opt *GoLogOption, msg string) {
	if lvl.Level() < this.opt.Level.Level() {
		return
	}
	var pc uintptr
	if !internal.IgnorePC {
		var pcs [1]uintptr
		runtime.Callers(callSkip+2, pcs[:])
		pc = pcs[0]
	}

	rec := this.getLogRecord()
	defer recPool.Put(rec)
	rec.SourcePC = pc
	rec.Level = lvl
	rec.Msg = msg
	rec.CreatedTime = time.Now()
	this.lk.Lock()
	defer this.lk.Unlock()
	if opt == nil {
		opt = &this.opt
	}
	this.handler.LogHandle(rec, opt)
}

func joinMsgStr(s string, args ...interface{}) string {
	if len(s) == 0 {
		return fmt.Sprint(args...)
	} else {
		if len(args) > 0 {
			return fmt.Sprintf(s, args...)
		}
		return s
	}
}

func (this *GoLog) LogMsgfWithOption(lvl GoLogLeveler, opt *GoLogOption, s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(lvl, opt.CallSkip+1, opt, msg)
}

func (this *GoLog) LogMsgWithSkip(lvl GoLogLeveler, skip int, msg string) {
	this.innerLogMsg(lvl, 1+skip, nil, msg)
}

func (this *GoLog) LogMsgfWithSkip(lvl GoLogLeveler, skip int, s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(lvl, 1+skip, nil, msg)
}

func (this *GoLog) LogMsgf(lvl GoLogLeveler, s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(lvl, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) LogMsg(lvl GoLogLeveler, msg string) {
	this.innerLogMsg(lvl, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Infof(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) GetOption() GoLogOption {
	return this.opt
}

func (this *GoLog) Info(s string) {
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, s)
}

func (this *GoLog) Debugf(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(LevelDebug, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Debug(s string) {
	this.innerLogMsg(LevelDebug, this.opt.CallSkip+1, nil, s)
}

func (this *GoLog) Warnf(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(LevelWarn, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Warn(s string) {
	this.innerLogMsg(LevelWarn, this.opt.CallSkip+1, nil, s)
}

func (this *GoLog) Print(v ...interface{}) {
	msg := fmt.Sprint(v...)
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Println(v ...interface{}) {
	msg := fmt.Sprint(v...)
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Printf(format string, v ...interface{}) {
	msg := joinMsgStr(format, v...)
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Fatal(v ...any) {
	msg := fmt.Sprint(v...)
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, msg)
	runtime.Gosched()
	time.Sleep(time.Millisecond * 100)
	os.Exit(1)
}

// Fatalf is equivalent to [Printf] followed by a call to [os.Exit](1).
func (this *GoLog) Fatalf(format string, v ...any) {
	msg := joinMsgStr(format, v...)
	this.innerLogMsg(LevelInfo, this.opt.CallSkip+1, nil, msg)
	runtime.Gosched()
	time.Sleep(time.Millisecond * 100)
	os.Exit(1)
}

func (this *GoLog) Errorf(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	this.innerLogMsg(LevelError, this.opt.CallSkip+1, nil, msg)
}

func (this *GoLog) Error(s string) {
	this.innerLogMsg(LevelError, this.opt.CallSkip+1, nil, s)
}

func SetDefaultLogger(lg *GoLog) {
	if lg == nil {
		// 设定为默认值
		lg = NewGoLog(WithFormatAndWriterHandler(os.Stdout, ""))
	}
	defaultLogger.Store(lg)
}

func init() {
	innerLog := NewGoLog(WithFormatAndWriterHandler(os.Stdout, ""))
	SetDefaultLogger(innerLog)
}

func Infof(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	DefaultLogger().innerLogMsg(LevelInfo, 1, nil, msg)
}

func Info(s string) {
	DefaultLogger().innerLogMsg(LevelInfo, 1, nil, s)
}

func Debugf(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	DefaultLogger().innerLogMsg(LevelDebug, 1, nil, msg)
}

func Debug(s string) {
	DefaultLogger().innerLogMsg(LevelDebug, 1, nil, s)
}

func Warnf(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	DefaultLogger().innerLogMsg(LevelWarn, 1, nil, msg)
}

func Warn(s string) {
	DefaultLogger().innerLogMsg(LevelWarn, 1, nil, s)
}

func Errorf(s string, args ...interface{}) {
	msg := joinMsgStr(s, args...)
	DefaultLogger().innerLogMsg(LevelError, 1, nil, msg)
}

func Error(s string) {
	DefaultLogger().innerLogMsg(LevelError, 1, nil, s)
}
